#pragma once
#include "canvas.h"
#include "scan_convert.h"

//// today: fixed point gouraud shading
//// today: floating point texture mapping

//// today: if late, z-buffer

namespace krryn{
namespace system_imp{
#pragma region Z-Buffer

	class z_buffer{
	public:
		bool operator()(){
			return true;
		}
	};

#pragma endregion

#pragma region Flat shader
	template<typename _Description>
	class scan_line_shade_flat{
	public:
		scan_line_shade_flat(view::pixel_t a_Color = 0xFF0000) : m_Color(a_Color) {}

		void shade(
			view::pixel_t *a_Scanline,
			const scan_convert<_Description> &a_Left, // no copies needed
			const scan_convert<_Description> &a_Right) // no copies needed
		{
			pixel_t *l_End		= a_Scanline + (int)a_Right.m_X;
			pixel_t *l_Current	= a_Scanline + (int)a_Left.m_X;

			while(l_Current != l_End) *l_Current++ = m_Color;
		}
	private:
		view::pixel_t m_Color;
	};
#pragma endregion

#pragma region Gouraud shader
	template<typename _Description>
	class scan_line_shade_gouraud{
	public:
		void shade(
			view::pixel_t *a_Scanline,
			scan_convert<_Description> a_Left,  // must be copies
			scan_convert<_Description> a_Right) // must be copies
		{
			int l_Start = (int)a_Left.m_X;

			pixel_t *l_Current	= a_Scanline + l_Start;
			pixel_t *l_End		= a_Scanline + (int)a_Right.m_X;

			float l_InvDeltaX = 1.f / (a_Right.m_X - a_Left.m_X);
			float l_Correction = (float)l_Start - a_Left.m_X;

			a_Left.recalculate_slopes(a_Right, l_InvDeltaX);
			a_Left.sub_pixel(l_Correction);

			while(l_Current != l_End){
				*l_Current++ = make_pixel(a_Left.m_R, a_Left.m_G, a_Left.m_B);
				a_Left.interpolate();
			}
		}
	private:
		view::pixel_t make_pixel(const float a_R, const float a_G, const float a_B){
			return
				  ((view::pixel_t)(a_R * 255) << 16)
				+ ((view::pixel_t)(a_G * 255) << 8)
				+ ((view::pixel_t)(a_B * 255));
		}
	};
#pragma endregion

	template<typename _Vertex>
	class triangle_renderer_software{
	public:
		typedef _Vertex vertex_type;
		typedef typename _Vertex::description_type description_type;

		triangle_renderer_software() : m_Canvas(0){}

		void set_canvas(view::canvas *a_Canvas) { m_Canvas = a_Canvas; }

		void draw(vertex_type &a_V1, vertex_type &a_V2, vertex_type &a_V3){
			// sort triangle by y-coordinate, from low to high: v1, v2, v3
			if(a_V1.get_position().m_Y > a_V2.get_position().m_Y)
				std::swap(a_V1, a_V2);
			if(a_V1.get_position().m_Y > a_V3.get_position().m_Y)
				std::swap(a_V1, a_V3);
			if(a_V2.get_position().m_Y > a_V3.get_position().m_Y)
				std::swap(a_V2, a_V3);

			float l_InvDeltaY[] = {
				1.f / (a_V3.get_position().m_Y - a_V1.get_position().m_Y),
				1.f / (a_V2.get_position().m_Y - a_V1.get_position().m_Y),
				1.f / (a_V3.get_position().m_Y - a_V2.get_position().m_Y)
			};

			if(l_InvDeltaY[0] == 0) return;

			float l_Temp[] = {
				  (a_V1.get_position().m_X - a_V3.get_position().m_X)
				* (a_V2.get_position().m_Y - a_V1.get_position().m_Y),

				  (a_V1.get_position().m_Y - a_V3.get_position().m_Y)
				* (a_V2.get_position().m_X - a_V1.get_position().m_X)
			};

			m_Scan[0].m_IsLeftSide = (l_Temp[0] - l_Temp[1]) <= 0.f;
			m_Scan[1].m_IsLeftSide = !m_Scan[0].m_IsLeftSide;

			m_Scan[0].init_edge(a_V3, a_V1, l_InvDeltaY[0]);

			if(l_InvDeltaY[1] != 0){
				m_Scan[1].init_edge(a_V2, a_V1, l_InvDeltaY[1]);

				draw_subtriangle(a_V1, a_V2, m_Scan[0], m_Scan[1]);
			}

			if(l_InvDeltaY[2] != 0){
				if(l_InvDeltaY[1] != 0){
					m_Scan[0].skip(
						a_V3, 
						a_V1.get_position().m_Y - a_V2.get_position().m_Y
					);
				}

				m_Scan[1].init_edge(a_V3, a_V2, l_InvDeltaY[2]);
				draw_subtriangle(a_V2, a_V3, m_Scan[0], m_Scan[1]);
			}
		}

	private:
		template<typename _Description>
		void draw_subtriangle(
			const _Vertex &a, 
			const _Vertex &b, 
			scan_convert<_Description> &a_Scan0, 
			scan_convert<_Description> &a_Scan1)
		{
			int l_Start = (int)a.get_position().m_Y;
			int l_End   = (int)b.get_position().m_Y;

			++l_Start;

			float l_Correction = (float)l_Start - a.get_position().m_Y;
			a_Scan0.sub_pixel(l_Correction);
			a_Scan1.sub_pixel(l_Correction);

			pixel_t *l_Scanline = m_Canvas->get_buffer() 
				+ l_Start * m_Canvas->get_pitch();

			// revert directions is needed
			scan_convert<description_type> *l_Scan[2];

			l_Scan[a_Scan0.m_IsLeftSide] = &a_Scan0;
			l_Scan[a_Scan1.m_IsLeftSide] = &a_Scan1;

			for(int y = l_Start; y <= l_End; ++y){
				m_Shader.shade(l_Scanline, *l_Scan[0], *l_Scan[1]);

				a_Scan0.interpolate();
				a_Scan1.interpolate();

				l_Scanline += m_Canvas->get_pitch();
			}
		}

		scan_convert<description_type> m_Scan[2];
		scan_line_shade_gouraud<description_type> m_Shader;
		view::canvas *m_Canvas;

		// we need:
		//  - guard bands
		//  - clipping
		//
		//  - vertex transformations (partially done)
		//  - fixed point triangle filler
		//
		//  - sub-pixel correctness (DONE)
		//  - texturing sub-texel / sub-pixel correct
		//  - mip-mapping
		//
		//  - Sutherland-Hodgeman clipping against Z = 0
		//  - Z-Buffer
		//  - Architecture

		// Extra
		//  - Trilinear, anisotropic filtering
		//  - Guard bands
		//  - Multi-texturing, alpha mapping
	};
}
}	